package pl.edu.agh.jemo.evolution.algorithm;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JPanel;

import org.apache.log4j.Logger;

import pl.edu.agh.jemo.conf.types.PrintableType;
import pl.edu.agh.jemo.evolution.genotype.Genotype;
import pl.edu.agh.jemo.evolution.genotype.impl.DoubleGenotype;
import pl.edu.agh.jemo.evolution.metrics.Metric;
import pl.edu.agh.jemo.evolution.metrics.impl.HypervolumeMetric;
import pl.edu.agh.jemo.evolution.metrics.impl.MaximumSpreadMetric;
import pl.edu.agh.jemo.evolution.metrics.impl.MinExtremaDistanceMetric;
import pl.edu.agh.jemo.evolution.metrics.impl.NnmMetric;
import pl.edu.agh.jemo.evolution.objfunc.ClassicObjectiveFunction;
import pl.edu.agh.jemo.evolution.objfunc.ObjectiveFunction;
import pl.edu.agh.jemo.evolution.objfunc.ObjectiveFunctionSet;
import pl.edu.agh.jemo.evolution.objfunc.impl.MichalewiczObjFunc;
import pl.edu.agh.jemo.evolution.operator.common.DomainControl;
import pl.edu.agh.jemo.evolution.operator.crossover.Crossover;
import pl.edu.agh.jemo.evolution.operator.mutation.Mutation;
import pl.edu.agh.jemo.evolution.operator.mutation.impl.Hillclimbing;
import pl.edu.agh.jemo.evolution.operator.mutation.impl.RadialMutation;
import pl.edu.agh.jemo.evolution.population.Population;
import pl.edu.agh.jemo.evolution.selections.Tournament;
import pl.edu.agh.jemo.evolution.specimen.Specimen;
import pl.edu.agh.jemo.gui.LoggerHelper;
import pl.edu.agh.jemo.gui.chart.Plot;
import pl.edu.agh.jemo.gui.chart.Plotter;

/**
 * Base class for all algorithm implementations.
 * Stores common parameters, used by all algorithms, like population size, number of generations, problem
 * definition, or genetic operators. It also defines scaffold for algorithm
 * runs. It performs initialization and steps in loop. Some common data are
 * initialized here as well. Also chart generation is handled by this class, so
 * that real algorithm creator has only to define exact population to be ploted.
 * As an extension of Thread class, AbstractAlgorithm creates new Thread to run
 * algorithm.
 * 
 * @author Marcin Zbijowski
 * 
 */
public abstract class AbstractAlgorithm extends Thread {

	protected Logger logger = LoggerHelper.getInstance().getLogger();
	// Created instances given in setters
	/**
	 * Main population reference. This population is treated as an input in
	 * every step. Algorithm requires constructed object reference to work.
	 */
	protected Population population;
	/**
	 * Crossover operator used to recombinate population.
	 * Algorithm requires constructed object reference to work.
	 */
	protected Crossover crossover;
	/**
	 * Muatation operator used to mutate specimen in population.
	 * Algorithm requires constructed object reference to work.
	 */
	protected Mutation mutation;
	/**
	 * Tournament operator used to eliminate weak specimen from population.
	 * Algorithm requires constructed object reference to work.
	 */
	
	protected Tournament tournament;
	/**
	 * Problem definition to be solved by algorithm,
	 * Algorithm requires constructed object reference to work.
	 */
	protected ObjectiveFunctionSet objectiveFunctionSet = null;
	
	protected ArrayList<Metric> metrics = new ArrayList<Metric>();

	// Parameters
	/**
	 * Number of iteration of algorithm, to be performed.
	 */
	protected long steps = 30;
	
	/**
	 * Initial population size.
	 */
	protected int populationSize = 500;
	
	/**
	 * Type of genotype used with specimen.
	 */
	protected Class<? extends Genotype> genotypeType = DoubleGenotype.class;
	/**
	 * Type of specimen to be used with algorithm.
	 * Note that some algorithms may require more specific Specimen type.
	 */
	protected Class<? extends Specimen> specimenType = Specimen.class;
	/**
	 * Instance of class responsible for chart generation.
	 */
	protected Plotter plotter;

	// read only for childs
	/**
	 * Number of currently performed step.
	 */
	protected int currentStep = 0;

	/**
	 * Abstract function called once on algorithm start.
	 * It is supposed to be overriden by designer of genetic algorithm, to perform his algorithm specific actions.
	 */
	protected abstract void performInit();

	/**
	 * Plots content of current population.
	 * This is done automatically on every step end. It is assumed, that this population is an output of algorithm after step.
	 * This method may be overriden, to plot different population reference in algorithm subclasses.
	 */
	protected void plotPopulation() {
		
		RadialMutation scatterMutation = new RadialMutation();
		calculatePhenotype();
		
		Plot plot = new Plot();
		plot.setName("Population");
		plot.addPopulation(population);
		plotter.addPlot(plot);
		
		scatterMutation.setDomainControl(DomainControl.IGNORE);
		scatterMutation.setImproveSpecimen(false);
		scatterMutation.setMutationChance(1.);
		scatterMutation.setRadius(.1);
		Population scattered = population.clone();
		for (Specimen specimen : scattered) {
			scatterMutation.performMutation(specimen);
		}
		Plot plot2 = new Plot();
		plot2.setName("ScatteredPopulation");
		plot2.addPopulation(scattered);
		plotter.addPlot(plot2);

	}

	/**
	 * Prepares algorithm to run. Performs required parameter initialization.
	 * Initializes population and plotter based on parameters stored in algorithm.
	 * Population is prepared with calculated phenotype and is printed by plotter.
	 * This methods also saves file with parameters used for this algorithm run.
	 * After that user defined operations from subclass are performed.
	 * 
	 * @throws IllegalAccessException if the specimen or genotype passed to has inaccessible nullary constructor.
	 * @throws InstantiationException if the specimen or genotype passed to algorithm has no nullary constructor.
	 * @throws IOException if config file or plot file were not created.
	 */
	protected void init() throws InstantiationException, IllegalAccessException,
			IOException {
		logger.debug("init");
		
		//metrics.add(new HypervolumeMetric(new BigDecimal(0.), new BigDecimal(0.)));
		//metrics.add(new MaximumSpreadMetric(new BigDecimal(0.), new BigDecimal(1.), new BigDecimal(2.), new BigDecimal(3.)));
		ArrayList<BigDecimal[]> extrema = new ArrayList<BigDecimal[]>();
		for (int i = 0 ; i < MichalewiczObjFunc.EXTREMA.length ; i++) {
			extrema.add(MichalewiczObjFunc.EXTREMA[i]);
		}
		//metrics.add(new MinExtremaDistanceMetric(extrema));
		//metrics.add(new NnmMetric(extrema, new BigDecimal(1.), 10));
		
		plotter = Plotter.getPlotter(objectiveFunctionSet.getBoundaries(),
				objectiveFunctionSet.getDimensions());
		logger.debug("plotter initialized");
		population.init(populationSize, objectiveFunctionSet, specimenType,
				genotypeType);
		BufferedWriter configOutput = new BufferedWriter(new FileWriter(
				new File(plotter.getOutputPath() + "config.txt")));
		configOutput.write(toString());
		configOutput.close();
		for (ObjectiveFunction fun : objectiveFunctionSet) {
			if (fun instanceof ClassicObjectiveFunction) {
				plotter.setBgFormula(((ClassicObjectiveFunction) fun)
						.getFormula());
				break;
			}
		}
		logger.debug("phenotype calculated");
		plotPopulation();
		plotter.endStep();
		performInit();
	}

	/**
	 * Returns String object containing description of algorithm configuration in readable form as a summary.
	 * 
	 * @return String description of algorithm configuration.
	 */	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("Algorithm class: " + getClass().getSimpleName() + "\n");
		sb.append("Steps: " + steps + "\n");
		sb.append("Population size: " + populationSize + "\n");
		sb.append("Genotype class: " + genotypeType.getSimpleName() + "\n");
		sb.append("Specimen class: " + specimenType.getSimpleName() + "\n");
		sb.append("Population class: " + population.getClass().getSimpleName()
				+ "\n\n");
		sb.append(objectiveFunctionSet.toString() + "\n\n");
		sb.append(mutation.toString() + "\n\n");
		sb.append(crossover.toString() + "\n\n");
		sb.append(tournament.toString() + "\n\n");
		return sb.toString();
	}

	/**
	 * Performs end algorithm cleanup.
	 * May be used differently in different algorithm implementations. One time end algorithm computations should be performed here.
	 * It could be for example some population rating summary.
	 */
	protected abstract void endAlgorithm();

	/**
	 * Performs operations that are repeated with every algorithm step.
	 * This is the main method of specific algorithm implementation. Here given operators should be used in order to get some results.
	 */
	protected abstract void step();

	/**
	 * Main method of every algorithm.
	 * It should be called with algorithm.start(), so algorithm can be run in a knew thread.
	 * In this method initial computations are done, then algorithm main loop is performed number of times specified in steps parameter.
	 * After all computations are done, end algorithm methods are performed and user defined cleanup is called.
	 * After every step, population stored under population parameter is plotted.
	 */
	@Override
	public void run() {
		try {
			logger.info("Algorithm started\n");
			init();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		for (currentStep = 0; currentStep < steps; currentStep++) {
			logger.info(String.format("--- Step %d started ---", currentStep+1));
			step();
			logger.info(String.format("--- Step %d finished ---\n", currentStep+1));
			plotPopulation();
			try {
				plotter.endStep();
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
		endAlgorithm();
		try {
			logger.info("Charts are being generated. Please have a sit and wait for a while.");
			plotter.releasePlotter();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		logger.info("Algorithm finished. Have a nice day.");
	}
	
	public void runWithSpecifiedPlotPath(final String plotPath) throws IOException {
		plotter = Plotter.getPlotter(objectiveFunctionSet.getBoundaries(), objectiveFunctionSet.getMinMaxValues(),
				objectiveFunctionSet.getDimensions(), plotPath);
		this.run();
	}

	/** 
	 * Calculates phenotype for all specimen stored in population stored in algorithm.
	 * This method sets phenotype of specimen to be equal to result of objective function performed on specimen genotype.
	 * This method is just a delegation of method call to simmilar population method. 
	 */
	protected void calculatePhenotype() {
		population.calculatePhenotype();
	}

	protected void calculateMetrics(Population resultPopulation) throws IOException {
		logger.debug("Calculating metrics");
		Plotter plotter = Plotter.getPlotter();
		for (Metric metric : metrics) {
			plotter.addMetric(metric.getValue(resultPopulation), metric.getName());
		}
	}
	
	/**
	 * Saves chosen by user problem.
	 * 
	 * @param newSet objective function set representing problem.
	 */
	public void setObjectiveFunctionSet(ObjectiveFunctionSet newSet) {
		objectiveFunctionSet = newSet;
	}

	/**
	 * Returns currenttly set number of steps expected by user.
	 * 
	 * @return Number representing how many steps should be performed.
	 */
	public long getSteps() {
		return steps;
	}

	/**
	 * Stores number of set required by user to be done.
	 * 
	 * @param generationsNumber Number of user expected steps.
	 */
	public void setSteps(long generationsNumber) {
		this.steps = generationsNumber;
	}

	/**
	 * Sets crossover instance to be used with algorithm.
	 * 
	 * @param crossover Constructed and initialized crossover object.
	 */
	public void setCrossover(Crossover crossover) {
		this.crossover = crossover;
	}

	/**
	 * Sets crossover instance to be used with algorithm.
	 * 
	 * @param mutation Constructed and initialized mutation object.
	 */
	public void setMutation(Mutation mutation) {
		this.mutation = mutation;
	}

	/**
	 * Sets tournament instance to be used with algorithm.
	 * 
	 * @param tournament Constructed and initialized tournament object.
	 */
	public void setTournament(Tournament tournament) {
		this.tournament = tournament;
	}

	/**
	 * Returns number of specimen created within population on algorithm initialization.
	 * 
	 * @return number of specimen to be created in population.
	 */
	public int getPopulationSize() {
		return populationSize;
	}

	/**
	 * Sets number of specimen to be created on algorithm initialization.
	 * 
	 * @param initPopulationSize Number of specimen used for population initialization.
	 */
	public void setPopulationSize(int initPopulationSize) {
		this.populationSize = initPopulationSize;
	}

	/**
	 * Returns number of currently performed step.
	 * 
	 * @return Current step number.
	 */
	public int getCurrentStep() {
		return currentStep;
	}

	/**
	 * Sets population instance to be used with algorithm.
	 * 
	 * @param population Constructed but not initialized population object.
	 */
	public void setPopulation(Population population) {
		this.population = population;
	}

	/**
	 * Sets type of genotype used for specimen in algorithm.
	 * 
	 * @param genotypeType Class object representing type which implements Genotype interface.
	 */
	public void setGenotypeType(Class<? extends Genotype> genotypeType) {
		this.genotypeType = genotypeType;
	}

	/**
	 * Sets type of specimen used in population by algorithm.
	 * 
	 * @param specimenType Class object representing type which extends Specimen class.
	 */
	public void setSpecimenType(Class<? extends Specimen> specimenType) {
		this.specimenType = specimenType;
	}

	/**
	 * Filters specimen types list and leaves these, that can be used with given algorithm.
	 * Not every algorithm can operate with all kinds of specimen, and this method allows user to get list of all specimen, that are applicable for algorithm.
	 *  
	 * @param specimenTypesList Collection of all specimen to be validated. Specimen types should be wrapped in PrintableType.
	 * @return List of specimen types valid for this algorithm type, wrapped in PrintableType instances.
	 */
	public abstract List<PrintableType<? extends Specimen>> validateSpecimenTypes(
			List<PrintableType<? extends Specimen>> specimenTypesList);

	/**
	 * Returns configuration panel being extension of JPanel instance.
	 * Returned panel is displayed by graphical user interface allowing user to configure algorithm.
	 * This method may return null if no configuration is possible.
	 * 
	 * @return Instance of JPanel with initialized components prepared to configure objects properties.
	 */
	public abstract JPanel getConfigPanel();
}
